package com.kancy.qrshare;

import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.Hashtable;
import java.util.Map;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.swing.ImageIcon;
import javax.swing.JOptionPane;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.kancy.qrshare.common.Constants;
import com.kancy.qrshare.common.Setting;
import com.kancy.qrshare.utils.ZipUtils;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;

/**
 * 使用二维码实现文件分享
 * @author kancy
 * @see 2018/07/30
 */
public class QrShare {

	public static void main(String[] arg) {

		try {
			// 1.获取文件路径，获取失败时，系统自动关闭
			File file = acquireAndCheckDownloadFile(arg);

			// 2.启动服务
			startJavaHttpFileServer(file);

			// 3.显示二维码
			String title = "  扫码下载文件（"+ file.getName() +"）";
			String content = "http://" + InetAddress.getLocalHost().getHostAddress().toString() + ":" + HTTP_PORT
					+ "/download"+ getFileSuffix(file) + "?id=" + System.currentTimeMillis();
			setSysClipboardText(content);
			createQrImageFrame(title, content);
			waitDelay(Setting.getProperty("wait_delay_time", Setting.wait_delay_time));

			// 4.提示是否强制关闭服务
			int unAccomplishedSize = accomplishMap.size();
			if(unAccomplishedSize > 0){
				int result = JOptionPane.showConfirmDialog(null, "目前存在"+ unAccomplishedSize+"个客户端正在下载，是否强制关闭服务？","系统关闭提示",2);
				if(result == 0){
					closeService();
					System.exit(0);
				}
			}

			// 5.系统退出
			waitAndExitSystem(0);

		} catch (Exception e) {
			alertAndExit("系统异常：" + e.getMessage());
		}
	}


	/**
	 * 检查和获取文件
	 * @param arg
	 * @return
	 */
	private static File acquireAndCheckDownloadFile(String[] arg) {
		if (arg == null || arg.length <= 0) {
			alertAndExit("请指定下载的文件全路径！");
		}

		// 获取本地文件
		String filePath = arg[0];
		File file = new File(filePath);

		if(!file.exists()){
			alertAndExit("文件不存在！");
		}

		// 如果是目录，先进行zip打包
		if(file.isDirectory()){
			filePath = packZipFile(file);
			file = new File(filePath);
		}

		if (!file.isFile()) {
			alertAndExit("文件不存在！");
		}
		return file;
	}

	/**
	 * 压缩目录
	 * @param dir
	 * @return
	 */
	private static String packZipFile(File dir) {
		String zipFilePath = dir.getParent().replace("\\", "/") + "/" + dir.getName() + ".zip";
		try {
			ZipUtils.zip(zipFilePath, dir);
		} catch (Exception e) {
			e.printStackTrace();
			alertAndExit("压缩目录异常：\n"+e.getMessage());
		}
		return zipFilePath;

	}

	/**
	 * 使用随机端口
	 * @return
	 */
	private static int getRandomPort() {
		Random random = new Random();
		String value = "";
		while(true){
			int num = random.nextInt(10);
			// 确保端口小于65535
			if(value.length() == 0 && (num == 0 || num > 5)){
				continue;
			}
			value += num;
			if(value.length() == 5){
				break;
			}
		}
		return Integer.parseInt(value);
	}

	/**
	 * 提示并且退出
	 * @param msg
	 */
	private static void alertAndExit(String msg) {
		JOptionPane.showMessageDialog(null, msg);
		System.exit(0);
	}

	/**
	 * 关闭服务
	 */
	private static void closeService() {
		if(!threadPool.isShutdown()){
			threadPool.shutdown();
		}
		server.stop(0);
	}

	/**
	 * 关闭系统
	 * @param i
	 * @throws InterruptedException
	 */
	private static void waitAndExitSystem(int i) {
		int unAccomplishedSize = accomplishMap.size();
		if(unAccomplishedSize == 0){
			closeService();
			System.out.println("系统即将关闭！");
			System.exit(0);
		}else{
			System.out.println("存在"+unAccomplishedSize+"个活动链接，请等待...");
			waitDelay(1000);
			waitAndExitSystem(i);
		}
	}

	/**
	 * 睡眠
	 * @param l
	 */
	private static void waitDelay(long l) {
		try {
			Thread.sleep(l);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	/**
	 * 将字符串复制到剪切板。
	 */
	private static void setSysClipboardText(String writeMe) {
		Clipboard clip = Toolkit.getDefaultToolkit().getSystemClipboard();
		Transferable tText = new StringSelection(writeMe);
		clip.setContents(tText, null);
	}

	/**
	 * 依赖 com.google.zxing core-3.0.0.jar
	 *
	 * @param title
	 * @param content
	 * @throws WriterException
	 */
	private static void createQrImageFrame(String title, String content) throws WriterException {
		Map<EncodeHintType, Object> hints = new Hashtable<EncodeHintType, Object>();
		hints.put(EncodeHintType.CHARACTER_SET, Setting.default_charset);
		BitMatrix matrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, 300, 300, hints);
		int width = matrix.getWidth();
		int height = matrix.getHeight();
		BufferedImage image = new BufferedImage(width, height, 1);
		for (int x = 0; x < width; x++) {
			for (int y = 0; y < height; y++) {
				image.setRGB(x, y, matrix.get(x, y) ? -16777216 : -1);
			}
		}
		ImageIcon icon = new ImageIcon(image);
		JOptionPane.showMessageDialog(null, "", title, 1, icon);
	}

	//-----------------------------------------------------------------------------
	// jdk HttpServer 提供http服务
	//-----------------------------------------------------------------------------

	private static final int MAX_POOL_NUM = Setting.http_pool_size > 0 ? Setting.http_pool_size : 10;

	private static final int HTTP_PORT = Setting.http_port > 0 ? Setting.http_port : getRandomPort();

	private static HttpServer server;

	private static ExecutorService threadPool = Executors.newFixedThreadPool(MAX_POOL_NUM);// 只允许10个用户同时下载

	private static Map<String, Boolean> accomplishMap = new Hashtable<String, Boolean>();// 防止未下载完成，关闭服务

	/**
	 * 开启http服务
	 * @param file
	 * @throws Exception
	 */
	private static void startJavaHttpFileServer(File file) throws Exception {
		server = HttpServer.create(new InetSocketAddress(HTTP_PORT), 0);
		server.setExecutor(threadPool);
		server.createContext("/download"+ getFileSuffix(file), new FileServerHandler(file));
		Thread httpServiceThread = new Thread(new Runnable() {
			public void run() {
				server.start();
				System.out.println("服务启动成功！");
			}
		});
		httpServiceThread.start();
	}

	/**
	 * 获取文件后缀
	 * @param file
	 * @return
	 */
	private static String getFileSuffix(File file) {
		String name = file.getName();
		String fileSuffix = "";
		if(name.contains(".")){
			fileSuffix = name.substring(name.lastIndexOf("."), name.length());
		}
		return fileSuffix.trim().toLowerCase();
	}

	/**
	 * 文件下载静态处理器
	 * @author kancy
	 */
	static class FileServerHandler implements HttpHandler {
		private File file;

		public FileServerHandler(File file) {
			this.file = file;
		}
		public void handle(HttpExchange exchange) throws IOException {

			if(accomplishMap.size() >= MAX_POOL_NUM){
				String msg = "服务队列已满，请稍候下载！";
				exchange.sendResponseHeaders(200, 0);
				exchange.getResponseBody().write(msg.getBytes(Setting.default_charset));
				exchange.getResponseBody().flush();
				exchange.getResponseBody().close();
				return;
			}

			String handlerId = UUID.randomUUID().toString();

			try {

				// 开始处理
				accomplishMap.put(handlerId, false);

				OutputStream os = exchange.getResponseBody();
				FileInputStream in = new FileInputStream(file);

				// 判断文件的类型
				// 1.图片类型/网页类型 直接显示在浏览器，通过另存为下载
				// 3.其他类型 下载模式
				String fileSuffix = getFileSuffix(file);
				String contentType = Constants.getContentType(fileSuffix);

				// 设置下载头
				if(Constants.CONTENT_TYPE_DEFAULT.equals(contentType)
						|| (!Setting.show_static_resource && !Constants.CONTENT_TYPE_DEFAULT.equals(contentType) )){
					exchange.getResponseHeaders().add("Content-Disposition", "attachment;filename=\""
							+ new String(file.getName().getBytes(Setting.default_charset), Setting.content_disposition_charset) + "\"");
				}
				exchange.getResponseHeaders().add("Content-Type", contentType);
				exchange.getResponseHeaders().add("Content-Length", String.valueOf(file.length()));

				// 发送响应
				exchange.sendResponseHeaders(200, file.length());

				// 写文件
				writeAndClose(os, in);

			} finally {
				// 处理完成
				accomplishMap.remove(handlerId);
			}
		}

		/**
		 * 写文件并且关闭流
		 * @param os
		 * @param in
		 * @throws IOException
		 */
		private static void writeAndClose(OutputStream os, FileInputStream in) throws IOException {
			try {
				byte[] buf = new byte[1024];
				int size = -1;
				while ((size = in.read(buf)) != -1) {
					os.write(buf, 0, size);
					os.flush();
				}
			} finally{
				if(os != null){
					os.close();
				}
				if(in != null){
					in.close();
				}
			}
		}
	}
}
